package org.lisy.java.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;

import org.apache.commons.compress.utils.IOUtils;

/**
 * IO 模型
 * 
 * @author lisy
 */
public class FileIoModeOperate {
	
	// 目标文件目录
	private final static String TARGET_MODE_FILE_DIR = "./deploy/target/mode";

	/**
	 * Linux 5 种 IO 模型
	 * 
	 *   阻塞式 IO 模型: 当用户线程发出 IO 请求之后，内核会去查看数据是否就绪，如果没有就绪就会等待数据就绪，而用户线程
	 * 就会处于阻塞状态，用户线程交出 CPU。当数据就绪之后，内核会将数据拷贝到用户线程，并返回结果给用户线程，用户线程才
	 * 解除 block 状态
	 * 
	 *   非阻塞 IO 模型: 在非阻塞 IO 模型中，用户线程需要不断地询问内核数据是否就绪，也就是非阻塞 IO 不会交出 CPU，而会
	 * 一直占用 CPU，导致 CPU 占用率非常高
	 *   
	 *   IO 复用模型: 有一个线程不断去轮询多个 socket 的状态，只有当 socket 真正有读写事件时，才真正调用实际的 IO 读写
	 * 操作.在 Java NIO 中，是通过 selector.select()去查询每个通道是否有到达事件，如果没有事件，则一直阻塞在那里，因此
	 * 这种方式会导致用户线程的阻塞
	 *   在非阻塞 IO 中，不断地询问 socket 状态时通过用户线程去进行的，而在多路复用 IO 中，轮询每个 socket 状态是内核在
	 * 进行的，这个效率要比用户线程要高的多
	 * 
	 *   信号驱动 IO 模型: 当用户线程发起一个 IO 请求操作，会给对应的 socket 注册一个信号函数，然后用户线程会继续执行，当
	 * 内核数据就绪时会发送一个信号给用户线程，用户线程接收到信号之后，在信号函数中调用 IO 读写操作进行实际的 IO 请求操作
	 * 
	 *   异步 IO 模型: 当用户线程发起 read 操作之 后，立刻就可以开始去做其它的事。而另一方面，从内核的角度，当它受到一个 
	 * asynchronous read 之后，它会立刻返回，说明 read 请求已经成功发起了，因此不会对用户线程产生任何 block。然后，内核会
	 * 等待数据准备完成，然后将数据拷贝到用户线程，当这一切都完成之后，内核会给用户线程发送一个信号，告诉它 read 操作完成了
	 *   也就说 用户线程完全不需要实际的整个 IO 操作是如何进行的，只需要先发起一个请求，当接收内核返回的成功信号时表示 IO 
	 * 操作已经完成，可以直接去使用数据了
	 *   
	 *   注意: 前面四种 IO 模型实际上都属于同步 IO，只有最后一种是真正的异步 IO，因为无论是多路复用 IO 还是信号驱动模型，IO 操作
	 * 的第 2 个阶段都会引起用户线程阻塞，也就是内核进行数据拷贝的过程都会让用户线程阻塞
	 */
	
	/**
	 * java IO 模型
	 *   BIO: Blocking I/O 同步并阻塞，数据的读取写入必须阻塞在一个线程内等待其完成，适用于连接数目比较小且固定的架构，这种方式
	 * 对服务器资源要求比较高，并发局限于应用中，JDK1.4 以前的唯一选择
	 *   NIO: New I/O，同时支持阻塞与非阻塞模式，原来的 I/O 以流的方式处理数据，而 NIO 以块的方式处理数据，适用于连接数目多且
	 * 连接比较短（轻操作）的架构，比如聊天服务器，并发局限于应用中，编程比较复杂，JDK1.4 开始支持
	 *   AIO: Asynchronous I/O，异步非阻塞 I/O 模型，适用于连接数目多且连接比较长（重操作）的架构，比如相册服务器，充分调用 OS 
	 * 参与并发操作，编程比较复杂，JDK7 开始支持
	 */
	
	/**
	 * Channel
	 * FileChannel 从文件中读写数据
	 * DatagramChannel 能通过UDP读写网络中的数据
	 * SocketChannel 能通过TCP读写网络中的数据
	 * ServerSocketChannel可以监听新进来的TCP连接，像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel
	 */
	
	/**
	 * Buffer 常用方法
	 * 分配 32 个字节大小的空间: 
	 *   ByteBuffer buffer = ByteBuffer.allocate(32);
	 * 写数据到Buffer有两种方式: 
	 *   1.buffer.put("hello".getBytes()); 从 Channel 写到 Buffer
	 *   2.channel.read(buffer); 通过 put() 方法写到 Buffer
	 * 从写模式切换到读模式，重置 position(0) 和 limit(position): 
	 *   buffer.flip();
	 * 从Buffer中读取数据有两种方式: 
	 *   1.Channel.write(buf); 从 buffer 中读取数据到 channel
	 *   2.byte[] readBuf = new byte[5]; buffer.get(readBuf); 使用 get() 方法从 Buffer 中读取数据
	 * 读取后清空缓冲区，以便下次读取写入: 
	 *   buffer.clear(); 
	 *   clear() 方法会清空整个缓冲区，compact() 方法只会清除已经读过的数据
	 * rewind()方法将position设回0，limit保持不变，可以重读 Buffer 中的所有数据
	 *   buffer.rewind()
	 * 标记 Buffer 中的一个特定 position，可以通过调用 reset() 方法恢复到这个 position
	 *   buffer.mark();
	 *   buffer.reset();
	 * 比较大小（注：剩余元素是从 position 到 limit 之间的元素）
	 *   equals() 足下列条件时，表示两个 Buffer 相等（比较剩余元素）: 
	 *   1.有相同的类型（byte、char、int等）
	 *   2.Buffer 中剩余的 byte、char 等的个数相等
	 *   3.Buffer 中所有剩余的 byte、char 等都相同
	 *   compareTo() 方法，满足下列条件，则认为一个 Buffer "小于"另一个 Buffer（比较剩余元素）: 
	 *   1.第一个不相等的元素小于另一个 Buffer 中对应的元素
	 *   2.所有元素都相等，但第一个 Buffer 比另一个先耗尽(第一个 Buffer 的元素个数比另一个少)
	 * 
	 * Buffer类型
	 * ByteBuffer、CharBuffer、MappedByteBuffer
	 * DoubleBuffer、FloatBuffer
	 * IntBuffer、LongBuffer、ShortBuffer
	 */
	
	public static void main(String[] args) {
		// bio 生成文件
		bioWrite();
		// bio 读取文件
		bioRead();
		
		// nio 生成文件
		nioWrite();
		// nio 读取文件
		nioRead();
		
		// aio 生成文件，通过Future写数据
		aioFutureWrite();
		// aio 读取文件，通过Future读数据
		aioFutureRead();
		
		// aio 生成文件，通过CompletionHandler写数据
		aioCompletionWrite();
		// aio 读取文件，通过CompletionHandler读数据
		aioCompletionRead();
	}

	private static void bioWrite() {
		System.out.println("===== bioWrite =====");
		User user = new User("lisy", 23);
		System.out.println(user); 
		ObjectOutputStream oos = null; 
		try {
			String filePath = TARGET_MODE_FILE_DIR + File.separator + "file_bio.txt";
			File file = new File(filePath);
        	if (file.exists()) {
        		file.delete();
        	} else {
        		// 如果上级不存在，就会自动创建
				file.getParentFile().mkdirs();
        	}
			oos = new ObjectOutputStream(new FileOutputStream(filePath));
			oos.writeObject(user); 
		} catch (IOException e) {
			e.printStackTrace(); 
		} finally {
			IOUtils.closeQuietly(oos);
		}
		System.out.println();
	}

	private static void bioRead() {
		System.out.println("===== bioRead =====");
		String filePath = TARGET_MODE_FILE_DIR + File.separator + "file_bio.txt";
		ObjectInputStream ois = null; 
		try {
			ois = new ObjectInputStream(new FileInputStream(new File(filePath))); 
			User newUser = (User) ois.readObject(); 
			System.out.println(newUser); 
		} catch (IOException e) {
			e.printStackTrace(); 
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally { 
			IOUtils.closeQuietly(ois);
		}
		System.out.println();
	}
	
	private static void nioWrite() {
		System.out.println("===== nioWrite =====");
		String filePath = TARGET_MODE_FILE_DIR + File.separator + "file_nio.txt";
		FileOutputStream fos = null; 
		try {
			File file = new File(filePath);
        	if (file.exists()) {
        		file.delete();
        	} else {
        		// 如果上级不存在，就会自动创建
				file.getParentFile().mkdirs();
        	}
			fos = new FileOutputStream(file); 
			FileChannel channel = fos.getChannel(); 
			ByteBuffer writeBuffer = ByteBuffer.allocate(32);
			System.out.println("ByteBuffer init mark: " + writeBuffer.mark()); 
			writeBuffer.put("hello world!".getBytes(Charset.forName("utf8")));
			System.out.println("ByteBuffer put mark: " + writeBuffer.mark()); 
			writeBuffer.flip();
			System.out.println("ByteBuffer flip mark: " + writeBuffer.mark()); 
			int length = channel.write(writeBuffer);
			System.out.println("write length: " + length);
			writeBuffer.clear();
			System.out.println("ByteBuffer clear mark: " + writeBuffer.mark());
			int num = 0;
			while (length != 0) {
				if (num > 0) {
					break;
				}
				writeBuffer.put("hahaha".getBytes());
				writeBuffer.flip();
				length = channel.write(writeBuffer);
				System.out.println("write length: " + length);
				writeBuffer.clear();
				num++;
			}
		} catch (IOException e) { 
			e.printStackTrace(); 
		} finally { 
			IOUtils.closeQuietly(fos);
		}
		System.out.println();
	}
	
	private static void nioRead() {
		System.out.println("===== nioRead =====");
		String filePath = TARGET_MODE_FILE_DIR + File.separator + "file_nio.txt";
		FileInputStream fin = null; 
		try {
			fin = new FileInputStream(new File(filePath)); 
			FileChannel channel = fin.getChannel();
			// 分配 10 个字节大小的空间
			ByteBuffer bf = ByteBuffer.allocate(10); 
			System.out.println("ByteBuffer init mark: " + bf.mark()); 
			int length = -1; 
			while ((length = channel.read(bf)) != -1) {
				System.out.println("ByteBuffer begin! mark: " + bf.mark());
				bf.flip();
				System.out.println("ByteBuffer flip! mark: " + bf.mark());
				byte[] bytes = new byte[length];
				bf.get(bytes);
				System.out.println(new String(bytes, 0, length));
				bf.clear();
				System.out.println("ByteBuffer clear! mark: " + bf.mark());
			}
			channel.close(); 
		} catch (IOException e) { 
			e.printStackTrace(); 
		} finally { 
			IOUtils.closeQuietly(fin);
		}
		System.out.println();
	}
	
	private static void aioFutureWrite() {
		System.out.println("===== aioFutureWrite =====");
		String filePath = TARGET_MODE_FILE_DIR + File.separator + "file_aio_future.txt";
    	AsynchronousFileChannel channel = null;
    	try {
    		File file = new File(filePath);
        	if (file.exists()) {
        		file.delete();
        	} else {
        		// 如果上级不存在，就会自动创建
    			file.getParentFile().mkdirs();
        	}
        	file.createNewFile();
        	Path path = Paths.get(filePath);
			channel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			long position = 0;
			buffer.put("Future Write".getBytes());
			buffer.flip();
			Future<Integer> operation = channel.write(buffer, position);
			buffer.clear();
			while(!operation.isDone()) {
				System.out.println("Write wait!");
			};
			System.out.println("Write done");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(channel);
		}
		System.out.println();
	}

	private static void aioFutureRead() {
		System.out.println("===== aioFutureRead =====");
		String filePath = TARGET_MODE_FILE_DIR + File.separator + "file_aio_future.txt";
		AsynchronousFileChannel fileChannel = null;
		try {
	    	Path path = Paths.get(filePath);
			fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			long position = 0;
			Future<Integer> operation = fileChannel.read(buffer, position);
			while(!operation.isDone()) {
				System.out.println("Read wait");
			}
			buffer.flip();
			byte[] data = new byte[buffer.limit()];
			buffer.get(data);
			System.out.println(new String(data));
			buffer.clear();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(fileChannel);
		}
		System.out.println();
	}

	private static void aioCompletionWrite() {
		System.out.println("===== aioCompletionWrite =====");
		String filePath = TARGET_MODE_FILE_DIR + File.separator + "file_aio_completion.txt";
    	AsynchronousFileChannel channel = null;
    	try {
    		File file = new File(filePath);
        	if (file.exists()) {
        		file.delete();
        	} else {
        		// 如果上级不存在，就会自动创建
    			file.getParentFile().mkdirs();
        	}
        	file.createNewFile();
        	Path path = Paths.get(filePath);
			channel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			long position = 0;
			buffer.put("Completion Write".getBytes());
			buffer.flip();
			channel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
			    @Override
			    public void completed(Integer result, ByteBuffer attachment) {
			        attachment.flip();
			        byte[] data = new byte[attachment.limit()];
			        attachment.get(data);
			        System.out.println("write result:" + result + ", attachment" + new String(data));
			        attachment.clear();
			        System.out.println("CompletionHandler write thread id: " + Thread.currentThread().getId());
			        System.out.println();
			    }
			    @Override
			    public void failed(Throwable exc, ByteBuffer attachment) {
			        System.out.println("Write failed");
			        exc.printStackTrace();
			    }
			});
			System.out.println("main thread id: " + Thread.currentThread().getId());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(channel);
		}
	}

	private static void aioCompletionRead() {
		System.out.println("===== aioCompletionRead =====");
		String filePath = TARGET_MODE_FILE_DIR + File.separator + "file_aio_completion.txt";
		AsynchronousFileChannel fileChannel = null;
		try {
	    	Path path = Paths.get(filePath);
			fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			long position = 0;
			fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
			    @Override
			    public void completed(Integer result, ByteBuffer attachment) {
			        attachment.flip();
			        byte[] data = new byte[attachment.limit()];
			        attachment.get(data);
			        System.out.println("read result:" + result + ", attachment" + new String(data));
			        attachment.clear();
			        System.out.println("CompletionHandler read thread id: " + Thread.currentThread().getId());
			        System.out.println();
			    }
			    @Override
			    public void failed(Throwable exc, ByteBuffer attachment) {
			    	System.out.println("Read failed");
			        exc.printStackTrace();
			    }
			});
			System.out.println("main thread id: " + Thread.currentThread().getId());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(fileChannel);
		}
	}
}

class User implements Serializable {
	private static final long serialVersionUID = 1L;
	
	private String name;
	private int age;
	
	public User(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
}
